#pragma once
#include <math.h>
#include <memory>
#include <memory.h>
#include <ostream>

template<class T = float>
class Matrix {
public:
    static Matrix<T> UnitMatrix()
    {
        Matrix<T> retval;
        memset(retval.ptr.get(), 0, sizeof(T) * 16);
        retval.ele(0, 0) = 1;
        retval.ele(1, 1) = 1;
        retval.ele(2, 2) = 1;
        retval.ele(3, 3) = 1;
        return retval;
    }

    static Matrix<T> TranslateMatrix(T x, T y, T z = 0)
    {
        Matrix<T> retval = UnitMatrix();
        retval.ele(0, 3) = x;
        retval.ele(1, 3) = y;
        retval.ele(2, 3) = z;
        return retval;
    }

    static Matrix<T> ScaleMatrix(T x, T y, T z = 1)
    {
        Matrix<T> retval = UnitMatrix();
        retval.ele(0, 0) *= x;
        retval.ele(1, 1) *= y;
        retval.ele(2, 2) *= z;
        return retval;
    }

    static Matrix<T> RotateMatrixZ(double angle)
    {
        Matrix<T> retval = UnitMatrix();
        double rad = angle * acos(-1) / 180;
        retval.ele(0, 0) = cos(rad);
        retval.ele(0, 1) = -sin(rad);
        retval.ele(1, 0) = sin(rad);
        retval.ele(1, 1) = cos(rad);
        return retval;
    }

    Matrix() = default;

    Matrix(const Matrix<T> &m)
    {
        memcpy(ptr.get(), m.ptr.get(), sizeof(T) * 16);
    }

    Matrix(Matrix<T> &&m)
    {
        ptr = std::move(m.ptr);
    }

    Matrix<T> &operator =(const Matrix<T> &m)
    {
        memcpy(ptr.get(), m.ptr.get(), sizeof(T) * 16);
        return *this;
    }

    Matrix<T> operator *(const Matrix<T> other)
    {
        Matrix<T> retval;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                retval.ele(i, j) = 0;
                for (int k = 0; k < 4; k++) {
                    retval.ele(i, j) += ele(i, k) * other.ele(k, j);
                }
            }
        }
        return retval;
    }

    operator T*() const
    {
        return ptr.get();
    }

    friend std::ostream &operator <<(std::ostream &os, const Matrix<T> &m)
    {
        os << std::endl;
        os << "Matrix {" << std::endl;
        os << "  [" << m.ele(0, 0) << "] [" << m.ele(0, 1) << "] [" << m.ele(0, 2) << "] [" << m.ele(0, 3) << "]" << std::endl;
        os << "  [" << m.ele(1, 0) << "] [" << m.ele(1, 1) << "] [" << m.ele(1, 2) << "] [" << m.ele(1, 3) << "]" << std::endl;
        os << "  [" << m.ele(2, 0) << "] [" << m.ele(2, 1) << "] [" << m.ele(2, 2) << "] [" << m.ele(2, 3) << "]" << std::endl;
        os << "  [" << m.ele(3, 0) << "] [" << m.ele(3, 1) << "] [" << m.ele(3, 2) << "] [" << m.ele(3, 3) << "]" << std::endl;
        os << "}" << std::endl;
        return os;
    }

private:
    inline T &ele(int i, int j) const
    {
        return ptr[i + j * 4];
    }

    std::unique_ptr<T[]> ptr = std::make_unique<T[]>(16);
};
